﻿using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Query;
using System.Linq.Expressions;
using System;

namespace Jack.EntityFrameworkCore
{
    public static class IQueryableExtensions
    {
        public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
        {
            using var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
            var relationalCommandCache = enumerator.Private("_relationalCommandCache");
            var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
            var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");

            var sqlGenerator = factory.Create();
            var command = sqlGenerator.GetCommand(selectExpression);

            string sql = command.CommandText;
            return sql;
        }

        private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
        private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);

        public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, Func<bool> condition)
        {
            if (condition() == false) return source;

            return source.Where(predicate);
        }

        public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate, bool condition = true)
        {
            if (condition == false) return source;

            return source.Where(predicate);
        }

        public static IQueryable<TEntity> SelectFields<TEntity>(this IQueryable<TEntity> query, params string[] fields)
        {
            if (fields.Length == 0) return query;

            ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity), "it");

            IEnumerable<MemberAssignment> bindings = fields.Select(field => Expression.Bind(typeof(TEntity).GetProperty(field), Expression.Property(parameterExpression, field)));
            MemberInitExpression body = Expression.MemberInit(Expression.New(typeof(TEntity)), bindings);

            Expression<Func<TEntity, TEntity>> lambda = Expression.Lambda<Func<TEntity, TEntity>>(body, parameterExpression);

            return query.Select(lambda);
        }
    }
}

// https://stackoverflow.com/questions/37527783/get-sql-code-from-an-entity-framework-core-iqueryablet
